﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Seagull.Configuration;
using Seagull.DAL;
using Seagull.Generic;
using Seagull.Model;

namespace Seagull.TestGeneric
{
    public static class SourceDataHelper
    {
        private static readonly IEnumerator<byte[]> _dataEnumerator = ReadTestData().GetEnumerator();

        public static CompressedDateInfo[] PrepareDatesWithData(IEnumerable<DateTime> dates)
        {
            return dates.Select(date =>
            {
                PrepareDateWithData(date);
                var dateDir = PathHelper.GetDateDirectory(Config.Instance.PathConfig.SourceDataFolder, date).FullName;
                return new CompressedDateInfo(dateDir, DirectoryHelper.GetSubDirs(dateDir),
                    DirectoryHelper.GetSubFiles(dateDir));
            }).ToArray();
        }

        private static void PrepareDateWithData(DateTime date)
        {
            var dateDir = new DirectoryInfo(Config.Instance.PathConfig.SourceDataFolder)
                .CreateSubdirectory(date.ToString(PathHelper.DateFormatString));
            PrepareFilesForDateDir(dateDir);
        }

        public static void PrepareFilesForDateDir(DirectoryInfo dateDir)
        {
            // TODO: Use real data.
            var ch1 = dateDir.CreateSubdirectory("CH1");
            File.Copy("sample.dat", Path.Combine(ch1.FullName, "00000.dat"), true);
            File.Copy("sample.dat", Path.Combine(ch1.FullName, "120000.dat"), true);
            var ch12 = dateDir.CreateSubdirectory("CH12");
            File.Copy("sample.dat", Path.Combine(ch12.FullName, "00000.dat"), true);
            File.Copy("sample.dat", Path.Combine(ch12.FullName, "120000.dat"), true);
            File.Copy("sample.dat", Path.Combine(ch12.FullName, "230000.dat"), true);
        }


        public static Frame[] AppendDataForSegment(IEnumerable<DateTime> timestamps, Segment segment)
        {
            var pathHelper = new PathHelper(Config.Instance.PathConfig.SourceDataFolder);
            using (var writer = new BinaryWriter(OpenSegmentFileForAppend(segment, pathHelper)))
            {
                var frames = new List<Frame>();
                foreach (var timestamp in timestamps)
                {
                    var data = NextFrameData();
                    WriteFrame(data, timestamp, writer);
                    frames.Add(new Frame(timestamp, data));
                }
                return frames.ToArray();
            }
        }

        public static byte[] NextFrameData()
        {
            _dataEnumerator.MoveNext();
            return _dataEnumerator.Current;
        }

        private static FileStream OpenSegmentFileForAppend(Segment segment, PathHelper pathHelper)
        {
            PathHelper.EnsureDirectoryRecursively(pathHelper.GetSegmentFile(segment).Directory);
            return
                FileHelper.Open(
                    () => pathHelper.GetSegmentFile(segment).Open(FileMode.Append, FileAccess.Write, FileShare.None));
        }

        public static Frame[] GenDataForSegment(Segment segment)
        {
            var timestamps = TimestampsOfHour(segment.StartTime);
            return AppendDataForSegment(timestamps, segment);
        }

        public static IEnumerable<DateTime> TimestampsOfHour(DateTime time)
        {
            var endTime = time.AddHours(1);
            var timestamp = time;
            while (timestamp < endTime)
            {
                yield return timestamp;
                timestamp = timestamp.AddMilliseconds(20);
            }
        }

        public static IEnumerable<byte[]> ReadTestData()
        {
            var testData = PrepareTestData().Take(100).ToArray();
            var count = 0;
            while (true)
            {
                yield return testData[count++];
                if (count >= 100)
                {
                    count = 0;
                }
            }
        }

        public static IEnumerable<byte[]> PrepareTestData()
        {
            using (var reader = new BinaryReader(File.OpenRead(@"sample.dat")))
            {
                var count = 0;
                while (true)
                {
                    if (count > 100)
                    {
                        reader.BaseStream.Seek(0, SeekOrigin.Begin);
                    }
                    yield return ReadFrame(reader);
                    ++count;
                    //                    Console.ReadLine();
                }
            }
        }

        public static byte[] ReadFrame(BinaryReader reader)
        {
            while (reader.ReadByte() != 0x7e)
            {
            }
            reader.ReadBytes(2);
            var frameType = ReadFrameType(reader);
            var channel = reader.ReadByte();
            var index = reader.ReadByte();

            var year = reader.ReadByte();
            var month = reader.ReadByte();
            var day = reader.ReadByte();
            var hour = reader.ReadByte();
            var minute = reader.ReadByte();
            var second = reader.ReadByte();
            var hundredMS = reader.ReadInt16();


            var dateTime = new DateTime(year, month, day, hour, minute, second);
#if VERBOSETEST
            Console.WriteLine("Channel {0}", channel);
            Console.WriteLine("Index {0}", index);
            Console.WriteLine(dateTime);
            Console.WriteLine(hundredMS);
#endif

            reader.ReadInt16();
            var bytes = reader.ReadBytes(200);
            reader.BaseStream.Seek(5, SeekOrigin.Current);
            return bytes;
        }

        public static FrameType ReadFrameType(BinaryReader reader)
        {
            var b = reader.ReadByte();
            switch (b)
            {
                case 0x11:
                    return FrameType.Phase;
                case 0x22:
                    return FrameType.Data;
                default:
                    throw new InvalidDataException();
            }
        }

        public static void WriteFrame(byte[] data, DateTime timestamp, BinaryWriter writer)
        {
            Action<int> writeByte = i => writer.Write((byte) i);
            writeByte(0x7e);

            writeByte(0x22);
            writeByte(0x1);
            writeByte(0);
            TimestampAccessor.Write(writer, timestamp);
            writer.Write((short) 0x00C8);
            writer.Write(data);

            writer.Write(0);
            writeByte(0x0D);
        }

        public static byte[] GeneratePhaseData(int phaseStartPos)
        {
            var rand = new Random();
            Func<byte> randByteNoFF = () => (byte) rand.Next(PhaseShifter.PhaseStartPosValue);
            var noStartPos = phaseStartPos < 0 || phaseStartPos >= Frame.DataLength;
            var phaseData = noStartPos
                ? GenArray(randByteNoFF)
                : Enumerable.Repeat(0, phaseStartPos)
                    .Concat(new[] {PhaseShifter.PhaseStartPosValue})
                    .Concat(Enumerable.Repeat(0, Frame.DataLength - phaseStartPos - 1))
                    .Select(i => i == 0 ? randByteNoFF() : (byte) i).ToArray();
            //            Assert.That(phaseData, Has.Length.EqualTo(Frame.DataLength));
            //            if(noStartPos)
            //            {
            //                Assert.That(phaseData, Has.None.EqualTo(PhaseShifter.PhaseStartPosValue));
            //            }
            //            else
            //            {
            //                Assert.That(phaseData, Has.Exactly(1).EqualTo(PhaseShifter.PhaseStartPosValue));
            //                Assert.That(phaseData[phaseStartPos], Is.EqualTo(PhaseShifter.PhaseStartPosValue));
            //            }
            return phaseData;
        }

        public static byte[] GenArray(Func<byte> genByte)
        {
            return Enumerable.Repeat(0, Frame.DataLength).Select(i => genByte()).ToArray();
        }
    }
}